﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
using Argos.Design;
using System.Workflow.ComponentModel;
using System.Reflection;
using System.Runtime.Serialization;
using System.IO;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.Xml;

namespace Argos.Data.Model
{
	/// <summary>
	/// Represents a test that is periodically executed when monitoring a target.
	/// </summary>
	[Serializable]
	[DefaultProperty("ActivityType")]
	[XmlRoot("test", Namespace = ItemBase.XmlNamespace)]
	public sealed class Test : ItemBase , IEquatable<Test> , IXmlSerializable
	{

		#region #ctor: public Test()

		/// <summary>
		/// Creates a new instance of the class.
		/// </summary>
		public Test() { this.ExecutionTimeout = TimeSpan.FromSeconds(5); }

		#endregion

		#region #ctor: internal Test(Type activityType, TimeSpan executionTimeout, Guid id, IDictionary<string, object> activityProperties)

		/// <summary>
		/// Supports the <see cref="TestConverter"/>.
		/// </summary>
		internal Test(Type activityType, TimeSpan executionTimeout, Guid id, IDictionary<string, object> activityProperties)
		{
			this.ActivityType = activityType;
			this.ExecutionTimeout = executionTimeout;
			this.Id = id;
			this.ActivityProperties = new Dictionary<string, object>(activityProperties);
		}

		#endregion



		#region public Dictionary<string, object> ActivityProperties { get; set; }

		private static string ActivityPropertiesPropertyName = "ActivityProperties";

		/// <summary>
		/// Sets or returns the properties that are set on any new activity created
		/// from the type specified in <see cref="ActivityType"/>.
		/// </summary>
		[Category("Test")]
		[DisplayName("Activity Properties")]
		[Description("Specifies the properties for new instance of the activity type specified in 'Activity'.")]
		[Editor(typeof(TestActivityPropertiesTypeEditor), typeof(UITypeEditor))]
		public Dictionary<string, object> ActivityProperties
		{
			get
			{
				Dictionary<string, object> props = this.GetProperty<Dictionary<string, object>>(ActivityPropertiesPropertyName);
				if (null == props)
				{
					props = new Dictionary<string, object>();
					this.SetProperty(ActivityPropertiesPropertyName, props);
				}
				return props;
			}
			set { this.SetProperty(ActivityPropertiesPropertyName, value); }
		}

		#endregion
		
		#region public Type ActivityType { get; set; }

		private static string ActivityTypePropertyName = "ActivityType";

		/// <summary>
		/// Sets or returns the type of the activity that is performing the test.
		/// </summary>
		[Category("Test")]
		[DisplayName("Activity")]
		[Description("Specifies the type of activity that is used when executing the test.")]
		[Editor(typeof(TestActivityTypeEditor), typeof(UITypeEditor))]
		[RefreshProperties(RefreshProperties.All)]
		public Type ActivityType
		{
			get { return this.GetProperty<Type>(ActivityTypePropertyName); }
			set
			{
				if (null == value)
				{
					throw new ArgumentNullException("ActivityType");
				}

				if (!value.IsTestActivity())
				{
					throw new ArgumentException("The given type is not a test activity type.", "ActivityType");
				}

				if (!object.Equals(this.ActivityType, value))
				{
					this.ActivityProperties.Clear();
					this.SetProperty(ActivityTypePropertyName, value);
				}
			}
		}

		#endregion
		
		#region public TimeSpan ExecutionTimeout { get; set; }

		private static string ExecutionTimeoutPropertyName = "ExecutionTimeout";

		/// <summary>
		/// Sets or returns the timeout for executing the test.
		/// </summary>
		/// <remarks>
		/// A test that takes longer to execute than the value specified
		/// in this property is considered to have failed.
		/// </remarks>
		[Category("Test")]
		[DisplayName("Test Timeout")]
		[Description("Specifies the longest time the test is allowed to execute before timing out.")]
		public TimeSpan ExecutionTimeout
		{
			get { return this.GetProperty<TimeSpan>(ExecutionTimeoutPropertyName); }
			set { this.SetProperty(ExecutionTimeoutPropertyName, value); }
		}

		#endregion



		#region public static Test Read(byte[] buffer)

		/// <summary>
		/// Reads the <see cref="Test"/> object stored in the given
		/// byte array and returns it.
		/// </summary>
		/// <param name="buffer">The buffer to read the test instance from.</param>
		public static Test Read(byte[] buffer)
		{
			return Test.Read<Test>(buffer);
		}

		#endregion
		
		#region public static Test Read(Stream stream)

		/// <summary>
		/// Reads the <see cref="Test"/> object stored in the given
		/// stream and returns it.
		/// </summary>
		/// <param name="stream">The stream to read the test instance from.</param>
		public static Test Read(Stream stream)
		{
			return Test.Read<Test>(stream);
		}

		#endregion
		
		#region public static Test Read(FileInfo sourceFile)

		/// <summary>
		/// Reads the <see cref="Test"/> object stored in the given
		/// file and returns it.
		/// </summary>
		/// <param name="sourceFile">The file to read the test instance from.</param>
		public static Test Read(FileInfo sourceFile)
		{
			return Test.Read<Test>(sourceFile);
		}

		#endregion

		public static Test ReadXml(Stream strm)
		{
			return ItemBase.ReadXml<Test>(strm);
		}

		public static Test ReadXml(FileInfo sourceFile)
		{
			return ItemBase.ReadXml<Test>(sourceFile);
		}



		#region public override bool Equals(object obj)

		/// <summary>
		/// Compares the given object to the current instance.
		/// </summary>
		/// <param name="obj">The object to compare to the current.</param>
		/// <returns>Returns true if the given object equals the current object instance.</returns>
		public override bool Equals(object obj)
		{
			if (obj is Test)
			{
				return this.Equals((Test)obj);
			}
			return false;
		}

		#endregion

		#region public bool Equals(Test other)

		/// <summary>
		/// Compares the current test instance to the given test instance.
		/// </summary>
		/// <param name="other">The test instance to compare to the current.</param>
		/// <returns>Returns true if the given test instance equals the current.</returns>
		public bool Equals(Test other)
		{
			if (null == other)
			{
				return false;
			}
			if (!object.Equals(this.ActivityType, other.ActivityType))
			{
				return false;
			}
			if (!object.Equals(this.ExecutionTimeout, other.ExecutionTimeout))
			{
				return false;
			}
			if (!object.Equals(this.Id, other.Id))
			{
				return false;
			}

			if (this.ActivityProperties.Count != other.ActivityProperties.Count)
			{
				return false;
			}

			List<string> propsThisKeys = new List<string>(this.ActivityProperties.Keys);
			List<string> propsOtherKeys = new List<string>(other.ActivityProperties.Keys);

			for (int i = 0; i < propsThisKeys.Count; i++)
			{
				if (!string.Equals(propsThisKeys[i], propsOtherKeys[i]))
				{
					return false;
				}
				if (!object.Equals(this.ActivityProperties[propsThisKeys[i]], other.ActivityProperties[propsOtherKeys[i]]))
				{
					return false;
				}
			}

			return true;
		}

		#endregion

		#region public override int GetHashCode()

		/// <summary>
		/// Returns the hash code for the test.
		/// </summary>
		public override int GetHashCode()
		{
			return base.GetHashCode();
		}

		#endregion
		
		#region public override string ToString()

		/// <summary>
		/// Returns the string representation of the test.
		/// </summary>
		public override string ToString()
		{
			if (null != this.ActivityType)
			{
				return this.ActivityType.FullName;
			}

			return null;
		}

		#endregion


		public XmlSchema GetSchema()
		{
			throw new NotImplementedException();
		}

		public void ReadXml(XmlReader reader)
		{
			if (ItemBase.XmlNamespace != reader.NamespaceURI)
			{
				throw new InvalidOperationException(string.Format("Unrecognized namespace: '{0}'.", reader.NamespaceURI));
			}

			if (reader.LocalName != "test")
			{
				throw new InvalidOperationException(string.Format("Unrecognized element name: '{0}'.", reader.LocalName));
			}

			Dictionary<string, object> properties = new Dictionary<string, object>();

			reader.ReadStartElement();

			while (reader.NodeType == XmlNodeType.Element)
			{
				switch (reader.LocalName)
				{
					case "id":
						this.Id = new Guid(reader.ReadElementContentAsString());
						break;

					case "activityProperties":
						reader.ReadElementContent(properties, "property", "name", "value");
						break;

					case "activityType":
						Type t = reader.ReadElementContentAsType();
						if (null != t)
							this.ActivityType = t;
						break;

					case "executionTimeout":
						this.ExecutionTimeout = TimeSpan.Parse(reader.ReadElementContentAsString());
						break;

					default:
						throw new InvalidOperationException(string.Format("Unrecognized element name: '{0}'.", reader.LocalName));
				}
			}

			reader.ReadEndElement();

			this.ActivityProperties = properties;
		}

		public void WriteXml(XmlWriter writer)
		{
			writer.WriteElementString("id", this.Id.ToString());

			writer.WriteDictionaryElement(this.ActivityProperties, "activityProperties", "property", "name", "value");

			if (null != this.ActivityType)
				writer.WriteTypeElement(this.ActivityType, "activityType");

			writer.WriteElementString("executionTimeout", this.ExecutionTimeout.ToString());
		}

	}

}
